package org.jpropelleralt.map.impl;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.jpropelleralt.change.Change;
import org.jpropelleralt.map.MapChange;
import org.jpropelleralt.map.MapDelta;

/**
 * Immutable implementation of {@link MapChange}
 */
public class MapChangeDefault implements MapChange {

	boolean shallow;
	List<MapDelta> mapDeltas;

	private MapChangeDefault(boolean shallow, List<MapDelta> mapDeltas) {
		super();
		this.shallow = shallow;
		this.mapDeltas = mapDeltas;
	}
	
	private MapChangeDefault(boolean shallow, MapDelta mapDelta) {
		this(shallow, makeSingleList(mapDelta));
	}
	
	private static List<MapDelta> makeSingleList(MapDelta delta) {
		List<MapDelta> list = new LinkedList<MapDelta>();
		list.add(delta);
		return Collections.unmodifiableList(list);
	}

	/**
	 * Create a deep change with a single {@link MapDelta} showing
	 * a complete alteration
	 * @param map 		The {@link Map} that has changed
	 * @return			The new change
	 */
	public static MapChangeDefault deep(Map<?, ?> map) {
		return new MapChangeDefault(false, MapDeltaDefault.newEntireMapAlteration(map));
	}
	
	/**
	 * Create a shallow change with a single {@link MapDelta}
	 * @param delta		The {@link MapDelta}
	 * @return			The new change
	 */
	public static MapChangeDefault shallow(MapDelta delta) {
		return new MapChangeDefault(true, delta);
	}
	
	@Override
	public Change extend(Change existing) {
		
		if (!(existing instanceof MapChange)) {
			throw new IllegalArgumentException("A MapChange can only extend another MapChange");
		}
		
		MapChange mapExisting = (MapChange)existing;
		
		//Extend different aspects of the change
		boolean extendsShallow = shallow || existing.shallow();
		
		//Extend the delta list by adding our own deltas to the end
		LinkedList<MapDelta> extendsDeltas = new LinkedList<MapDelta>();
		for (MapDelta delta : mapExisting) {
			extendsDeltas.add(delta);
		}
		for (MapDelta delta : this) {
			extendsDeltas.add(delta);
		}

		return new MapChangeDefault(extendsShallow, extendsDeltas);
	}
	
	@Override
	public Iterator<MapDelta> iterator() {
		return mapDeltas.iterator();
	}

	@Override
	public boolean shallow() {
		return shallow;
	}

	@Override
	public String toString() {
		return (shallow ? "Shallow ":"Deep ") + "Map Change";
	}

}
